package com.tomgibara.cluster.gvm.space;

import java.util.Arrays;
import java.util.List;

import com.tomgibara.cluster.ClusterPainter;
import com.tomgibara.cluster.gvm.GvmResult;
import com.tomgibara.cluster.gvm.GvmSpace;

public class GvmVectorSpace extends GvmSpace {

	// statics
	
	private static double[] coords(Object obj) {
		return (double[]) obj;
	}

	// fields
	
	private final int dimensions;
	
	// constructors
	
	public GvmVectorSpace(int dimensions) {
		if (dimensions < 1) throw new IllegalArgumentException("non-positive dimensions");
		this.dimensions = dimensions;
	}
	
	// accessors
	
	public int getDimensions() {
		return dimensions;
	}
	
	// space factory methods
	
	@Override
	public double[] newOrigin() {
		return new double[dimensions];
	}

	@Override
	public double[] newCopy(Object pt) {
		return coords(pt).clone();
	}

	// space point operations
	
	@Override
	public double magnitudeSqr(Object pt) {
		double sum = 0.0;
		double[] coords = coords(pt);
		for (int i = 0; i < dimensions; i++) {
			double c = coords[i];
			sum += c * c;
		}
		return sum;
	}
	
	@Override
	public double sum(Object pt) {
		double sum = 0.0;
		for (double coord : coords(pt)) {
			sum += coord;
		}
		return sum;
	}

	@Override
	public void setToOrigin(Object pt) {
		Arrays.fill(coords(pt), 0.0);
	}

	@Override
	public void setTo(Object dstPt, Object srcPt) {
		System.arraycopy(coords(srcPt), 0, coords(dstPt), 0, dimensions);
	}

	@Override
	public void setToScaled(Object dstPt, double m, Object srcPt) {
		double[] dstCoords = coords(dstPt);
		double[] srcCoords = coords(srcPt);
		for (int i = 0; i < dimensions; i++) {
			dstCoords[i] = m * srcCoords[i];
		}
	}

	@Override
	public void setToScaledSqr(Object dstPt, double m, Object srcPt) {
		double[] dstCoords = coords(dstPt);
		double[] srcCoords = coords(srcPt);
		for (int i = 0; i < dimensions; i++) {
			double c = srcCoords[i];
			dstCoords[i] = m * c * c;
		}
	}

	@Override
	public void add(Object dstPt, Object srcPt) {
		double[] dstCoords = coords(dstPt);
		double[] srcCoords = coords(srcPt);
		for (int i = 0; i < dimensions; i++) {
			dstCoords[i] += srcCoords[i];
		}
	}

	@Override
	public void addScaled(Object dstPt, double m, Object srcPt) {
		double[] dstCoords = coords(dstPt);
		double[] srcCoords = coords(srcPt);
		for (int i = 0; i < dimensions; i++) {
			dstCoords[i] += m * srcCoords[i];
		}
	}

	@Override
	public void addScaledSqr(Object dstPt, double m, Object srcPt) {
		double[] dstCoords = coords(dstPt);
		double[] srcCoords = coords(srcPt);
		for (int i = 0; i < dimensions; i++) {
			double c = srcCoords[i];
			dstCoords[i] += m * c * c;
		}
	}

	@Override
	public void subtract(Object dstPt, Object srcPt) {
		double[] dstCoords = coords(dstPt);
		double[] srcCoords = coords(srcPt);
		for (int i = 0; i < dimensions; i++) {
			dstCoords[i] -= srcCoords[i];
		}
	}

	@Override
	public void subtractScaled(Object dstPt, double m, Object srcPt) {
		double[] dstCoords = coords(dstPt);
		double[] srcCoords = coords(srcPt);
		for (int i = 0; i < dimensions; i++) {
			dstCoords[i] -= m * srcCoords[i];
		}
	}

	@Override
	public void subtractScaledSqr(Object dstPt, double m, Object srcPt) {
		double[] dstCoords = coords(dstPt);
		double[] srcCoords = coords(srcPt);
		for (int i = 0; i < dimensions; i++) {
			double c = srcCoords[i];
			dstCoords[i] -= m * c * c;
		}
	}

	@Override
	public void scale(Object pt, double m) {
		double[] coords = coords(pt);
		for (int i = 0; i < dimensions; i++) {
			coords[i] *= m;
		}
	}

	@Override
	public void square(Object pt) {
		double[] coords = coords(pt);
		for (int i = 0; i < dimensions; i++) {
			coords[i] *= coords[i];
		}
	}
	
	// optimizations
	
	@Override
	public double distance(Object pt1, Object pt2) {
		double[] coords1 = coords(pt1);
		double[] coords2 = coords(pt2);
		double sum = 0.0;
		for (int i = 0; i < dimensions; i++) {
			double d = coords1[i] - coords2[i];
			sum += d * d;
		}
		return Math.sqrt(sum);
	}
	
	@Override
	public double variance(double m, Object pt, Object ptSqr) {
		double[] coords = coords(pt);
		double[] sqrCoords = coords(ptSqr);
		double sum = 0.0;
		for (int i = 0; i < dimensions; i++) {
			double c = coords[i];
			sum += sqrCoords[i] - c * c / m;
		}
		return sum;
	}
	
	@Override
	public double variance(double m1, Object pt1, Object ptSqr1, double m2, Object pt2) {
		double[] coords1 = coords(pt1);
		double[] sqrCoords1 = coords(ptSqr1);
		double[] coords2 = coords(pt2);

		double m0 = m1 + m2;
		double sum = 0.0;
		for (int i = 0; i < dimensions; i++) {
			double c2 = coords2[i];
			double c = coords1[i] + m2 * c2;
			double cSqr = sqrCoords1[i] + m2 * c2 * c2;
			sum += cSqr - c * c / m0;
		}
		return sum;
	}

	@Override
	public double variance(double m1, Object pt1, Object ptSqr1, double m2, Object pt2, Object ptSqr2) {
		double[] coords1 = coords(pt1);
		double[] sqrCoords1 = coords(ptSqr1);
		double[] coords2 = coords(pt2);
		double[] sqrCoords2 = coords(ptSqr2);
		
		double m0 = m1 + m2;
		double sum = 0.0;
		for (int i = 0; i < dimensions; i++) {
			double c = coords1[i] + coords2[i];
			double cSqr = sqrCoords1[i] + sqrCoords2[i];
			sum += cSqr - c * c / m0;
		}
		
		return sum;
	}
	
	@Override
	public String toString(Object pt) {
		return Arrays.toString(coords(pt));
	}
	
}
